// Toggle button implementation for Gtk+.
// Copyright 2006 (C) Ralph Thomas

#include <view/toggle_button.h>
#include <binder.hpp>
#include <gtk/gtk.h>
#include <gtk/os_utilities.hpp>

namespace view {
	void toggle_when_equal( GtkWidget* widget, const adobe::value_t& a, const adobe::value_t& b ) {
		bool active( a == b );
		if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(widget) ) == active ) return;
		gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(widget), active );
	}
	std::string get_bold_string( const std::string& source ) {
		return std::string( "<b>" ) + source + "</b>";
	}
	//
	/// Hold everything required for the toggle button in one place.
	//
	struct toggle_button_t {
		typedef adobe::implementation::signal_wrapper_zero_t<void> on_toggle_t;
		adobe::auto_ptr<GtkWidget>	control_m;
		adobe::binder_helper_t		datapump_m;
		adobe::value_t			on_value_m;
		adobe::value_t			off_value_m;
		on_toggle_t			toggle_m;
		std::string			name_m;

		toggle_button_t( adobe::sheet_t& sheet, const adobe::value_t& bind, const adobe::value_t& on_value, const adobe::value_t& off_value, const std::string& name ) : datapump_m( sheet ), on_value_m( on_value ), off_value_m( off_value ), name_m( name ) {
			control_m.reset( gtk_toggle_button_new_with_label( name.c_str() ) );
			//
			// We don't want an indicator.
			//
			gtk_toggle_button_set_mode( GTK_TOGGLE_BUTTON(control_m.get()), false );
			//
			// Connect ourselves to the datapump.
			//
			datapump_m.initialize( bind,
				boost::bind( &toggle_when_equal, control_m.get(), on_value_m, _1 ),
				boost::bind( &gtk_widget_set_sensitive, control_m.get(), _1 )
				);
			//
			// Respond to user input.
			//
			toggle_m.set_callback( boost::bind( &toggle_button_t::toggled, this ) );
			toggle_m.connect( control_m.get(), "clicked" );
		}
		//
		/// Toggled is called both when something is selected and unselected,
		/// so we have to have this predicate in place.
		//
		void toggled() {
			if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(control_m.get()) ) ) {
				datapump_m.set( on_value_m );
				gtk_label_set_markup( GTK_LABEL(gtk_bin_get_child(GTK_BIN(control_m.get()))), get_bold_string( name_m ).c_str() );
			} else if( GTK_WIDGET_HAS_FOCUS( control_m.get() ) ) {
				if( off_value_m != adobe::value_t() ) {
					datapump_m.set( off_value_m );
					gtk_button_set_label( GTK_BUTTON(control_m.get()), name_m.c_str() );
					return;
				}
				gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(control_m.get()), true );	
			} else {
				gtk_button_set_label( GTK_BUTTON(control_m.get()), name_m.c_str() );
			}
		}
		adobe::extents_t best_bounds() {
			//
			// We calculate the best bounds for when we are bold.
			//
			gtk_label_set_markup( GTK_LABEL(gtk_bin_get_child(GTK_BIN(control_m.get()))), get_bold_string( name_m ).c_str() );
			adobe::extents_t bounds = adobe::implementation::best_bounds_widget( control_m.get() );
			gtk_button_set_label( GTK_BUTTON(control_m.get()), name_m.c_str());
			return bounds;
		}
			
	};
	adobe::widget_node_t toggle_button_factory(
		const adobe::dictionary_t&	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token )
	{
		//
		// Both "bind" and "on_value" are mandatory.
		//
		adobe::value_t bind = parameters.at( adobe::static_name_t( "bind" ) );
		adobe::value_t on_value = parameters.at( adobe::static_name_t( "on_value" ) );
		std::string name;
		if( parameters.count( adobe::static_name_t( "name" ) ) )
			name = parameters.get<std::string>( adobe::static_name_t( "name" ) );
		//
		// "off_value" makes this more like a checkbox than a radio button.
		//
		adobe::value_t off_value;
		if( parameters.count( adobe::static_name_t( "off_value" ) ) )
			off_value = parameters.at( adobe::static_name_t( "off_value" ) );
		//
		// Make the widget.
		//
		toggle_button_t* button( new toggle_button_t( token.sheet_m, bind, on_value, off_value, name ) );
		token.assemblage_m.delete_on_destruction( button );
		//
		// Insert the widget into the given parent.
		//
		GtkWidget* ref = button->control_m.get();
		adobe::display_t::position_t display_token( adobe::insert( adobe::get_main_display(), parent.display_token_m, ref ) );
		adobe::display_t::position_t overlay_token( token.overlay_m.insert( parent.overlay_token_m ) );
		//
		// Bind the calculate and place signals for Eve.
		//
		adobe::eve_t::signal_suite_t signals;
		eve_client::place_func_t placer = boost::bind( &adobe::implementation::set_bounds_widget, ref, _1, _2 );
		signals.eve_calculate_proc_m = boost::bind( &eve_client::utility_widget_calculate, button->best_bounds(), _1 );
		signals.eve_place_proc_m = boost::bind( &eve_client::utility_widget_place, placer, _1, _2, boost::ref( token.overlay_m ), overlay_token );
		//
		// Register the widget with Eve.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					adobe::eve_t::calculate_data_t(),
					false,
					parameters,
					signals )
				)
			);
		//
		// Return the new toggle button.
		//
		return adobe::widget_node_t( parent.size_m, eve_token, display_token, overlay_token );
	}
};
	
